// debug.sp - TF2Stern Debug Output
//  by Thomas 'nickmame' Roth

/* File Structure:
 *  SternDebugInit()
 *  SternDebugClose()
 *  SetDebugLevel callback
 *  SetDebugMethod callback
 *  OpenDebugFile()
 *  WriteDebug(level, message, variables...)
 *  CloseDebugFile()
 */

/////////////////////////////////////////////////////////////////////////////
// CONSTANTS
/////////////////////////////////////////////////////////////////////////////
enum  // DebugPriority - values associted with st_debug_level cvar
{
  dpNone,   // output nothing
  dpError,  // output detected errors
  dpHigh,   // output important stuff
  dpFunc,   // output function calls
  dpLow,    // output function steps
  dpEvent   // output hooked event calls (spam city)
};

enum  // DebugMethod - values associated with st_debug_method cvar
{
  dmFile,   // output to DEBUG_FILENAME
  dmServ,   // output to server console
  dmGame    // output to game clients
};

// debug filename for the debug method dmFile
#define DEBUG_FILENAME "tf2stern.log"

/////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
/////////////////////////////////////////////////////////////////////////////
new Handle:ghDebugLevel = INVALID_HANDLE,
    cvDebugLevel;   // amount of debug information

new Handle:ghDebugMethod = INVALID_HANDLE,
    cvDebugMethod;  // output method for debug information

// debug file for the debug method dmFile
new Handle:ghDebugFile = INVALID_HANDLE;

/////////////////////////////////////////////////////////////////////////////
// SternDebugInit()
//  initializes the debug cvars
/////////////////////////////////////////////////////////////////////////////
SternDebugInit()
{
  ghDebugLevel = CreateConVar("st_debug_level", "1",
    "sets the amount of debug information displayed",
    FCVAR_PLUGIN | FCVAR_NOTIFY, true, 0.0, true, 5.0);
  HookConVarChange(ghDebugLevel, SetDebugLevel);
  cvDebugLevel = GetConVarInt(ghDebugLevel);
  
  ghDebugMethod = CreateConVar("st_debug_method", "1",
    "sets the method for recording debug information",
    FCVAR_PLUGIN, true, 0.0, true, 2.0);
  HookConVarChange(ghDebugMethod, SetDebugMethod);
  cvDebugMethod = GetConVarInt(ghDebugMethod);
  
  ghDebugFile = INVALID_HANDLE;
}

/////////////////////////////////////////////////////////////////////////////
// SternDebugClose()
//  disables the debug cvars
/////////////////////////////////////////////////////////////////////////////
SternDebugClose()
{
  CloseDebugFile();
  UnhookConVarChange(ghDebugLevel, SetDebugLevel);
  UnhookConVarChange(ghDebugMethod, SetDebugMethod);
}

/////////////////////////////////////////////////////////////////////////////
// SetDebugLevel callback
//  handles the st_debug_level cvar
/////////////////////////////////////////////////////////////////////////////
public SetDebugLevel(Handle:convar, const String:oldValue[],
  const String:newValue[])
{
  cvDebugLevel = StringToInt(newValue);
  WriteDebug(dpHigh, "Debug level changed from %s to %s", oldValue, newValue);
}

/////////////////////////////////////////////////////////////////////////////
// SetDebugMethod callback
//  handles the st_debug_method cvar
/////////////////////////////////////////////////////////////////////////////
public SetDebugMethod(Handle:convar, const String:oldValue[],
  const String:newValue[])
{
  // close the old debug method
  WriteDebug(dpHigh, "Debug method changed to %s", newValue);
  if( StringToInt(oldValue) == dmFile )
  {
    CloseDebugFile();
  }
  
  // open the new debug method
  cvDebugMethod = StringToInt(newValue);
  if( StringToInt(newValue) == dmFile )
  {
    OpenDebugFile();
  }
  
  WriteDebug(dpHigh, "Debug method changed from %s", oldValue);
}

/////////////////////////////////////////////////////////////////////////////
// OpenDebugFile()
//  opens DEBUG_FILENAME in append mode with the handle ghDebugFile
/////////////////////////////////////////////////////////////////////////////
OpenDebugFile()
{
  // guard against bad function calls
  if( ghDebugFile == INVALID_HANDLE )
  {
    // open debug file in append mode
    ghDebugFile = OpenFile(DEBUG_FILENAME, "a");
    if( ghDebugFile == INVALID_HANDLE )
    {
      // console error if file cannot be opened
      PrintToServer("TF2Stern :: unable to access file %s", DEBUG_FILENAME);
    }
    else
    {
      decl String:timestr[32];
      new time = GetTime();
      
      // print debug session header (dpNone hack to avoid priority)
      FormatTime(timestr, sizeof(timestr), "%m/%d/%Y - %H:%M:%S", time);
      WriteDebug(dpNone, "---%s", timestr);
    }
  }
}

/////////////////////////////////////////////////////////////////////////////
// WriteDebug(level, message, variables...)
//  writes message to debug output if priority is below the current debug level
/////////////////////////////////////////////////////////////////////////////
WriteDebug(priority, const String:template[], any:...)
{
  // output messages within the debug level
  if( priority <= cvDebugLevel )
  {
    decl String:message[128];
    
    // fill the format variables in the message template
    VFormat(message, sizeof(message), template, 3);
    
    // select the output method
    switch( cvDebugMethod )
    {
      case dmFile:  // debug file
      {
        if( !WriteFileLine(ghDebugFile, message) )
        {
          // route to server console if write file fails
          PrintToServer("TF2Stern :: %s", message);
        }
      }
      case dmServ:  // server console
      {
        PrintToServer("TF2Stern :: %s", message);
      }
      case dmGame:  // game clients
      {
        PrintToChatAll("TF2Stern :: %s", message);
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////
// CloseDebugFile()
//  closes the file handle ghDebugFile
/////////////////////////////////////////////////////////////////////////////
CloseDebugFile()
{
  // guard against bad function calls
  if( ghDebugFile != INVALID_HANDLE )
  {
    // print debug session footer (dpNone hack to avoid priority)
    WriteDebug(dpNone, "---Debug Session Ended\n");
    
    // close debug file
    CloseHandle(ghDebugFile);
    ghDebugFile = INVALID_HANDLE;
  }
}
